import cluster, {Worker} from 'cluster';
import os from 'os';
import _ from 'lodash';

import communication from './src/utils/Communication';
import setup from './server';

export enum BaseMessageType {
    GetLandlordPid = 'get-landlord-pid',
    GotLandlordPid = 'got-landlord-pid',
}

export interface Message {
    type: string;
    to?: number;
    msg?: any;
}

class ProcessManger {
    private static async ForkCluster(): Promise<Worker> {
        return cluster.fork();
    }

    private static async workerInit() {
        setup(process.pid === await communication.getLandlordPid());
    }

    private landlord: Worker | null = null;
    private readonly workers: Worker[] = [];

    constructor() {
        cluster.isMaster ? this.masterInit() : ProcessManger.workerInit();
    }

    private async masterInit() {
        const startNumber: number = process.env.NODE_ENV !== 'production' ? 2 : os.cpus().length;

        for (let i = 0; i < startNumber; i++) {
            const worker = await ProcessManger.ForkCluster();
            if (!this.landlord) {
                this.landlord = worker;
            }
            this.wrapMessage(worker);
            this.workers.push(worker);
        }

        this.daemon();
    }

    private wrapMessage(worker: Worker) {
        worker.on('message', (msg: Message) => {
            for (const w of this.workers) {
                if (w.process.pid === msg.to) {
                    w.send?.(msg);
                    return;
                }
            }
            if (msg.type === BaseMessageType.GetLandlordPid) {
                worker.send({
                    type: BaseMessageType.GotLandlordPid,
                    msg: this.landlord?.process.pid,
                });
            }
        });
    }

    private daemon() {
        cluster.on('exit', async (worker) => {
            const index = _.findIndex(this.workers, {id: worker.id});
            this.workers.splice(index, 1);
            const newWorker = await ProcessManger.ForkCluster();
            if (worker.id === this.landlord?.id) {
                this.landlord = newWorker;
            }
            this.wrapMessage(newWorker);
        });
    }
}

const processManger = new ProcessManger();
